home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Sample Code / SCSI Samples 1.0 / SCSI Simple Sample 06⁄15 ƒ / Src / LogManager.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-06-16  |  33.5 KB  |  1,318 lines  |  [TEXT/KAHL]

  1. /*                                    LogManager.c                                */
  2. /*
  3.  * LogManager.c
  4.  * Copyright © 1993 Apple Computer Inc. All rights reserved.
  5.  *
  6.  * These functions manage a logging display for error messages and other text.
  7.  * The log is implemented as a ListManager list that can hold nLogItems. This
  8.  * module is intentionally more-or-less self-contained so it can easily be
  9.  * exported to other applications.
  10.  */
  11. #include "LogManager.h"
  12. #ifndef THINK_C                /* MPW includes            */
  13. #include <Errors.h>
  14. #include <Script.h>
  15. #include <Types.h>
  16. #include <Files.h>
  17. #include <Resources.h>
  18. #include <QuickDraw.h>
  19. #include <Fonts.h>
  20. #include <Events.h>
  21. #include <Windows.h>
  22. #include <ToolUtils.h>
  23. #include <Memory.h>
  24. #include <Menus.h>
  25. #include <Lists.h>
  26. #include <Printing.h>
  27. #include <Dialogs.h>
  28. #include <StandardFile.h>
  29. #endif
  30. #include <Serial.h>
  31. #include <Printing.h>
  32. #include <Packages.h>
  33. #pragma segment LogManager
  34.  
  35. /*
  36.  * There is a 32000 byte maximum for the list, so don't
  37.  * make nLogLines too big.
  38.  */
  39. #ifndef nDefaultLogLines
  40. #define nDefaultLogLines    128
  41. #endif
  42. #define width(r)                ((r).right - (r).left)
  43. #define height(r)                ((r).bottom - (r).top)
  44. #ifndef TRUE
  45. #define TRUE                    1
  46. #define FALSE                    0
  47. #endif
  48. enum {
  49.     kScrollBarWidth     = 16,
  50.     kScrollBarOffset    = kScrollBarWidth - 1,
  51.     kActiveControl        = 0,        /* Normal button hilite                */
  52.     kDisabledControl    = 255        /* Disabled button hilite            */
  53. };
  54. /*
  55.  * When the horizontal scrollbar is at zero, the list is indented +4 pixels.
  56.  * When the horizontal scrollbar increases, the list indent decreases. This
  57.  * value is "by inspection," but we could extract it from the list record
  58.  * when the list is initially created.
  59.  */
  60. enum {
  61.     kZeroIndent            = 4
  62. };
  63. /*
  64.  * This sets a nominal value for the maximum cell width. This might be
  65.  * better provided as a user-settable parameter.
  66.  */
  67. #define kMaxHorizontalScroll    (CharWidth('M') * 255)
  68. static const char        endOfLine[1] = { 0x0D };    /* <CR>            */
  69.  
  70. /*
  71.  * Cheap 'n dirty pascal string copy routine.
  72.  */
  73. #ifndef pstrcpy
  74. #define pstrcpy(dst, src) do {                            \
  75.         StringPtr    _src = (src);                        \
  76.         BlockMove(_src, dst, _src[0] + 1);                \
  77.     } while (0)
  78. #endif
  79. /*
  80.  * Cheap 'n dirty pascal string concat.
  81.  */
  82. #ifndef pstrcat
  83. #define pstrcat(dst, src) do {                            \
  84.         StringPtr        _dst = (dst);                    \
  85.         StringPtr        _src = (src);                    \
  86.         short            _len;                            \
  87.         _len = 255 - _dst[0];                            \
  88.         if (_len > _src[0]) _len = _src[0];                \
  89.         BlockMove(&_src[1], &_dst[1] + _dst[0], _len);    \
  90.         _dst[0] += _len;                                \
  91.     } while (0)
  92. #endif
  93.  
  94. static OSErr                        AddStringToList(
  95.         ListHandle                        logListHandle,
  96.         ConstStr255Param                theString
  97.     );
  98. static pascal void                    ScrollLogAction(
  99.         register ControlHandle            theControl,
  100.         short                            partcode
  101.     );
  102. static void                            ScrollLogList(
  103.         ControlHandle                    theControl
  104.     );
  105. static StringHandle                    GetLogStringHandle(
  106.         ListHandle                        logListHandle,
  107.         short                            theRow
  108.     );
  109.  
  110. #define LIST            (**logListHandle)
  111. #define LOGINFO            (**((LogInfoHdl) (LIST.userHandle)))
  112. #define HSCROLL            (LOGINFO.hScroll)
  113. #define IS_COLOR(port)    (((((CGrafPtr) (port))->portVersion) & 0xC000) != 0)
  114. #define COLOR_LIST        (IS_COLOR(LIST.port))
  115.  
  116. /*
  117.  * This code sequence is copied into the ListProc handle. It is designed so we
  118.  * don't have to flush the instruction and data caches. Note that this must
  119.  * be revised if you compile for a non-68000 environment.
  120.  */
  121. static const short gDummyLDEF[] = {
  122.         0x207A,                /*        movea.l    procPtr,a0            */
  123.         0x0004,                /*                <offset to procPtr>    */
  124.         0x4ED0,                /*        jmp        a0                     */
  125.         0x0000, 0x0000        /*        dc.l    <Drawing Proc here>    */
  126. };
  127.  
  128. static pascal void
  129. LogListDefProc(
  130.         short                    listMessage,
  131.         Boolean                    listSelect,
  132.         Rect                    *listRect,
  133.         Cell                    listCell,
  134.         short                    listDataOffset,
  135.         short                    listDataLen,
  136.         ListHandle                errorLogList
  137.     );
  138.  
  139. /*
  140.  * Create the data display list.
  141.  */
  142. ListHandle
  143. CreateLog(
  144.         const Rect                    *viewRect,
  145.         short                        listFontNumber,
  146.         short                        listFontSize,
  147.         short                        logLines
  148.     )
  149. {
  150.         OSErr                        status;
  151.         ListHandle                    logListHandle;
  152.         FontInfo                    info;
  153.         Point                        cellSize;
  154.         short                        listHeight;
  155.         Rect                        dataBounds;
  156.         Rect                        listRect;
  157.         short                        listFontHeight;
  158.         Handle                        drawProcHdl;
  159.         LogInfoRecord                logInfo;
  160.         Handle                        logInfoHdl;
  161.         ProcPtr                        listProcPtr;
  162.         short                        horizontalMax;
  163.         
  164.         logInfoHdl = NULL;
  165.         drawProcHdl = NULL;
  166.         if (logLines == 0)
  167.             logLines = nDefaultLogLines;
  168.         TextFont(listFontNumber);
  169.         TextSize(listFontSize);
  170.         GetFontInfo(&info);
  171.         listFontHeight = info.ascent + info.descent + info.leading;
  172.         /*
  173.          * Compute the list drawing area, adjusting the list
  174.          * area height so an integral number of lines will be drawn. As with
  175.          * the standard list manager, the scroll bars will be drawn outside
  176.          * of the list rectangle.
  177.          */
  178.         listRect = *viewRect;
  179.         /*
  180.          * Normalize the list area so that an integral number of cells are visible
  181.          * on the display and define the visual shape of each cell.
  182.          */
  183.         listHeight = height(listRect);
  184.         listHeight -= (listHeight % listFontHeight);
  185.         listRect.bottom = listRect.top + listHeight;
  186.         SetPt(&cellSize, width(listRect), listFontHeight);
  187.         /*
  188.          * Note: we create a one-column list with both vertical and horizontal
  189.          * scrollbars, then we steal the horizontal scrollbar because we're
  190.          * scrolling within the list cell. Unfortunately, the List Manager only
  191.          * scrolls from one cell (column in the horizontal direction) to another.
  192.          */
  193.         SetRect(&dataBounds, 0, 0, 1, 0);
  194.         logListHandle = LNew(
  195.                 &listRect,                            /* Viewing area                */
  196.                 &dataBounds,                        /* Rows and col's            */
  197.                 cellSize,                            /* Element size                */
  198.                 0,                                    /* No defProc yet            */
  199.                 qd.thePort,                            /* Display window            */
  200.                 TRUE,                                /* Draw it                    */
  201.                 FALSE,                                /* No grow box                */
  202.                 TRUE,                                /* Has horizontal scroll    */
  203.                 TRUE                                /* Has vertical scroll        */
  204.             );
  205.         if (logListHandle == NULL)
  206.             goto failure;
  207.         LIST.selFlags = lOnlyOne;
  208.         LIST.listFlags = lDoVAutoscroll;            /* Vertical autoscroll only    */
  209.         logInfo.logFileRefNum = 0;
  210.         logInfo.logFileVRefNum = 0;
  211.         logInfo.logFileStatus = 0;
  212.         logInfo.logLines = logLines;
  213.         logInfo.fontNumber = listFontNumber;
  214.         logInfo.fontSize = listFontSize;
  215.         if (COLOR_LIST) {
  216.             GetForeColor(&logInfo.foreColor);
  217.             GetBackColor(&logInfo.backColor);
  218.         }
  219.         status = PtrToHand(&logInfo, &logInfoHdl, sizeof logInfo);
  220.         if (status != noErr)
  221.             goto failure;
  222.         LIST.userHandle = (Handle) logInfoHdl;
  223.         HSCROLL = LIST.hScroll;                        /* Grab horizontal scroller    */
  224.         LIST.hScroll = NULL;                        /* Remove it from the list    */
  225.         SetCRefCon(HSCROLL, (long) logListHandle);    /* Link scrollbar to list    */
  226.         status = PtrToHand(gDummyLDEF, &drawProcHdl, sizeof gDummyLDEF);
  227.         if (status != noErr)
  228.             goto failure;
  229.         listProcPtr = (ProcPtr) LogListDefProc;
  230.         BlockMove(&listProcPtr, &((short *) *drawProcHdl)[3], sizeof listProcPtr);
  231.         LIST.listDefProc = drawProcHdl;
  232.         horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
  233.         if (horizontalMax < 0)
  234.             horizontalMax = 0;
  235.         SetCtlMin(HSCROLL, 0);
  236.         SetCtlMax(HSCROLL, horizontalMax);
  237.         SetCtlValue(HSCROLL, 0);
  238.         HiliteControl(
  239.             HSCROLL,
  240.             (horizontalMax == 0) ? kDisabledControl : kActiveControl
  241.         );
  242.         goto success;
  243. failure:
  244.         if (drawProcHdl != NULL) {
  245.             DisposeHandle((Handle) drawProcHdl);
  246.             LIST.listDefProc = NULL;
  247.         }
  248.         DisposeLog(logListHandle);
  249.         logListHandle = NULL;
  250. success:
  251.         return (logListHandle);
  252. }
  253.  
  254. /*
  255.  * DisposeLog disposes of our private information and then disposes of the list.
  256.  */
  257. void
  258. DisposeLog(
  259.         ListHandle                        logListHandle
  260.     )
  261. {
  262.         if (logListHandle != NULL) {
  263.             if (LIST.userHandle != NULL) {
  264.                 LIST.hScroll = HSCROLL;            /* The list manager disposes    */ 
  265.                 DisposeHandle(LIST.userHandle);
  266.                 LIST.userHandle = NULL;
  267.             }
  268.             LDispose(logListHandle);
  269.         }
  270. }
  271.  
  272. /*
  273.  * UpdateLogWindow explicitly updates the log's window. It is generally called only
  274.  * after Modal Dialogs or Alerts obscured the window.
  275.  */
  276. void
  277. UpdateLogWindow(
  278.         ListHandle                        logListHandle
  279.     )
  280. {
  281.         WindowPtr                        theWindow;
  282.         GrafPtr                            savePort;
  283.         Rect                            viewRect;
  284.         RgnHandle                        listRgn;
  285.         RgnHandle                        clipRgn;
  286.         
  287.         if (logListHandle != NULL) {
  288.             theWindow = (WindowPtr) LIST.port;
  289.             if (EmptyRgn(((WindowPeek) theWindow)->updateRgn) == FALSE) {
  290.                 viewRect = LIST.rView;
  291.                 if (LIST.hScroll != NULL)
  292.                     viewRect.bottom += kScrollBarWidth;
  293.                 if (LIST.hScroll != NULL)
  294.                     viewRect.right += kScrollBarWidth;
  295.                 listRgn = NewRgn();
  296.                 RectRgn(listRgn, &viewRect);
  297.                 SectRgn(listRgn, ((WindowPeek) theWindow)->updateRgn, listRgn);
  298.                 if (EmptyRgn(listRgn) == FALSE) {
  299.                     /*
  300.                      * We have something to redraw. Fake an update event handler.
  301.                      */
  302.                     GetPort(&savePort);
  303.                     SetPort(theWindow);
  304.                     clipRgn = NewRgn();
  305.                     GetClip(clipRgn);
  306.                     SetClip(listRgn);
  307.                     EraseRgn(listRgn);
  308.                     UpdateLog(logListHandle);
  309.                     SetClip(clipRgn);
  310.                     DisposeRgn(clipRgn);
  311.                     ValidRgn(listRgn);
  312.                     SetPort(savePort);
  313.                 }
  314.                 DisposeRgn(listRgn);
  315.             }
  316.         }
  317. }
  318.  
  319. /*
  320.  * UpdateLog redraws the list.
  321.  */
  322. void
  323. UpdateLog(
  324.         ListHandle                        logListHandle
  325.     )
  326. {
  327.         Rect                        viewRect;
  328.         RGBColor                    saveForeColor;
  329.         RGBColor                    saveBackColor;
  330.         
  331.         if (logListHandle != NULL) {
  332.             /*
  333.              * Make sure the list is locked down while we draw. Note that we
  334.              * assume that the application has called UpdateControls, so
  335.              * the horizontal scrollbar is correctly drawn.
  336.              */        
  337.             if (COLOR_LIST) {
  338.                 GetForeColor(&saveForeColor);
  339.                 GetBackColor(&saveBackColor);
  340.                 RGBForeColor(&LOGINFO.foreColor);
  341.                 RGBBackColor(&LOGINFO.backColor);
  342.             }
  343.             viewRect = LIST.rView;
  344.             /*
  345.              * Include the scrollbars in the frame.
  346.              */
  347.             EraseRect(&viewRect);
  348.             InsetRect(&viewRect, -1, -1);
  349.             viewRect.right += kScrollBarOffset;
  350.             viewRect.bottom += kScrollBarOffset;
  351.             FrameRect(&viewRect);
  352.             LUpdate(LIST.port->visRgn, logListHandle);
  353.             if (COLOR_LIST) {
  354.                 RGBForeColor(&saveForeColor);
  355.                 RGBBackColor(&saveBackColor);
  356.             }
  357.         }
  358. }
  359.  
  360. /*
  361.  * ActivateLog activates (or deactivates) the log. Call it on activate and
  362.  * suspendResume events.
  363.  */
  364. void
  365. ActivateLog(
  366.         ListHandle                        logListHandle,
  367.         Boolean                            activating
  368.     )
  369. {
  370.         if (logListHandle != NULL) {
  371.             LActivate(activating, logListHandle);
  372.             HiliteControl(
  373.                 HSCROLL,
  374.                 (activating) ? kActiveControl : kDisabledControl);
  375.         }
  376. }
  377.  
  378. /*
  379.  * MoveLog Repositions the log list area.
  380.  */
  381. void
  382. MoveLog(
  383.         ListHandle                        logListHandle,
  384.         short                            leftEdge,
  385.         short                            topEdge
  386.     )
  387. {
  388.         Rect                            viewRect;
  389.         
  390.         if (logListHandle != NULL) {
  391.             if (LIST.rView.left != leftEdge || LIST.rView.top != topEdge) {
  392.                 viewRect = LIST.rView;
  393.                 InsetRect(&viewRect, -1, -1);
  394.                 InvalRect(&viewRect);
  395.                 OffsetRect(
  396.                     &LIST.rView,
  397.                     leftEdge - LIST.rView.left,
  398.                     topEdge -LIST.rView.top
  399.                 );
  400.                 viewRect = LIST.rView;
  401.                 InsetRect(&viewRect, -1, -1);
  402.                 InvalRect(&viewRect);
  403.                 MoveControl(
  404.                     LIST.vScroll,
  405.                     LIST.rView.right - kScrollBarOffset,
  406.                     LIST.rView.top - 1
  407.                 );
  408.                 MoveControl(
  409.                     HSCROLL,
  410.                     LIST.rView.left - 1,
  411.                     LIST.rView.bottom - kScrollBarOffset
  412.                 );
  413.             }
  414.         }
  415. }
  416.  
  417. /*
  418.  * SizeLog: this is the list rectangle size and does not include the
  419.  * horizontal and vertical scrollbars.
  420.  */
  421. void
  422. SizeLog(
  423.         ListHandle                        logListHandle,
  424.         short                            newWidth,
  425.         short                            newHeight
  426.     )
  427. {
  428.         Rect                            viewRect;
  429.         Point                            cellSize;
  430.         short                            horizontalMax;
  431.         
  432.         if (logListHandle != NULL) {
  433.             viewRect = LIST.rView;
  434.             InsetRect(&viewRect, -1, -1);
  435.             InvalRect(&viewRect);
  436.             /*
  437.              * We put the horizontal scrollbar back into the list record so that
  438.              * the list manager kindly resizes it. Then we take it out again.
  439.              */
  440.             LIST.hScroll = HSCROLL;
  441.             LSize(newWidth, newHeight, logListHandle);
  442.             LIST.hScroll = NULL;
  443.             cellSize = LIST.cellSize;
  444.             cellSize.h = width(LIST.rView);
  445.             LCellSize(cellSize, logListHandle);
  446.             horizontalMax = kMaxHorizontalScroll - width((**HSCROLL).contrlRect);
  447.             if (horizontalMax < 0)
  448.                 horizontalMax = 0;
  449.             SetCtlMax(HSCROLL, horizontalMax);
  450.             HiliteControl(
  451.                 HSCROLL,
  452.                 (horizontalMax == 0) ? kDisabledControl : kActiveControl
  453.             );
  454.             viewRect = LIST.rView;
  455.             InsetRect(&viewRect, -1, -1);
  456.             InvalRect(&viewRect);
  457.         }
  458. }
  459.  
  460. /*
  461.  * DoClickInLog: call this when there is a mouse down in the log area (or in
  462.  * one of the scrollbars.
  463.  */
  464. Boolean
  465. DoClickInLog(
  466.         ListHandle                        logListHandle,
  467.         const EventRecord                *eventRecord
  468.     )
  469. {
  470. #define EVENT    (*eventRecord)
  471.  
  472.         Point                            mousePt;
  473.         Boolean                            result;
  474.         Rect                            viewRect;
  475.         short                            part;
  476.         ControlHandle                    theControl;
  477.  
  478.         if (logListHandle == NULL)
  479.             result = FALSE;
  480.         else {
  481.             mousePt = EVENT.where;
  482.             GlobalToLocal(&mousePt);
  483.             /*
  484.              * Handle clicks in the horizontal scrollbar: Do not pass them through
  485.              * LClick, as it does not do what we want and besides, the scrollbar
  486.              * isn't there any more.
  487.              */
  488.             if (PtInRect(mousePt, &(**HSCROLL).contrlRect)) {
  489.                 part = FindControl(mousePt, (**HSCROLL).contrlOwner, &theControl);
  490.                 if (part >= 0 && theControl == HSCROLL) {
  491.                     if (part == inThumb) {
  492.                         if (TrackControl(theControl, mousePt, NULL))
  493.                             ScrollLogList(theControl);
  494.                     }
  495.                     else {
  496.                         TrackControl(theControl, mousePt,
  497.                                     NewControlActionProc(ScrollLogAction));
  498.                     }
  499.                 }
  500.             }
  501.             else {
  502.                 viewRect = LIST.rView;
  503.                 viewRect.right += kScrollBarOffset;
  504.                 result = PtInRect(mousePt, &viewRect);
  505.                 if (result)
  506.                     (void) LClick(mousePt, EVENT.modifiers, logListHandle);
  507.             }
  508.         }
  509.         return (result);
  510. #undef EVENT
  511. }
  512.  
  513. /*
  514.  * Log errors.
  515.  */
  516. void
  517. LogStatus(
  518.         ListHandle                        logListHandle,
  519.         OSErr                            theError,
  520.         const StringPtr                    infoText
  521.     )
  522. {
  523.         Handle                            macErrorHdl;
  524.         Str255                            msg;
  525.         Str15                            errorValue;
  526.         
  527.         if (logListHandle != NULL && theError != noErr) {
  528.             pstrcpy(msg, infoText);
  529.             pstrcat(msg, "\p: ");
  530.             NumToString(theError, errorValue);
  531.             pstrcat(msg, errorValue);
  532.             macErrorHdl = GetResource('Estr', theError);
  533.             if (macErrorHdl != NULL) {
  534.                 pstrcat(msg, "\p ");
  535.                 pstrcat(msg, (StringPtr) *macErrorHdl);
  536.                 ReleaseResource(macErrorHdl);
  537.             }
  538.             DisplayLogString(logListHandle, msg);
  539.         }
  540. }
  541.  
  542. /*
  543.  * DisplayLogString
  544.  * Call this function to store a string in the list.
  545.  */
  546. void
  547. DisplayLogString(
  548.         ListHandle                        logListHandle,
  549.         const StringPtr                    theString
  550.     )
  551. {
  552.         short                        theRow;
  553.         Boolean                        scrollAtBottom;
  554.         
  555.         if (logListHandle != NULL) {
  556.             /*
  557.              * If there are already logLines in the
  558.              * list, delete the first row of the list.
  559.              * Then, in any case, append this datum at the
  560.              * bottom.
  561.              *
  562.              * The scroll bars are managed as follows:
  563.              * scroll bar is at the bottom, the new datum
  564.              * is selected and autoscrolled into view.
  565.              * Otherwise, the current cell is unchanged.
  566.              */
  567.             theRow = LIST.dataBounds.bottom;
  568.             scrollAtBottom =
  569.                 (GetCtlValue(LIST.vScroll) == GetCtlMax(LIST.vScroll));
  570.             if (theRow >= LOGINFO.logLines)
  571.                 LDelRow(1, 0, logListHandle);
  572.             if (AddStringToList(logListHandle, theString) == noErr) {
  573.                 if (scrollAtBottom)
  574.                     LScroll(0, LIST.dataBounds.bottom - GetCtlValue(LIST.vScroll), logListHandle);
  575.                 if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr)
  576.                     WriteLogLine(logListHandle, theString);
  577.             }
  578.         }
  579. failure:
  580.         return;
  581. }
  582.  
  583. static OSErr
  584. AddStringToList(
  585.         ListHandle                        logListHandle,
  586.         ConstStr255Param                theString
  587.     )
  588. {
  589.         StringHandle                    stringHandle;
  590.         Cell                            theCell;
  591.         
  592.         stringHandle = NewString(theString);
  593.         if (stringHandle != NULL) {
  594.             theCell.h = 0;
  595.             theCell.v = LAddRow(1, LIST.dataBounds.bottom, logListHandle);
  596.             if (MemError() == noErr)
  597.                 LSetCell(&stringHandle, sizeof stringHandle, theCell, logListHandle);
  598.         }
  599.         return (MemError());
  600. }
  601.  
  602. /*
  603.  * ScrollLogAction is called by the Toolbox while executing the TrackControl
  604.  * routine.  It has to take care of scrolling the log when the user clicks on the
  605.  * up/down arrow or page parts of the scroll bar.
  606.  */
  607. static pascal void
  608. ScrollLogAction(
  609.         register ControlHandle            theControl,
  610.         short                            partcode
  611.     )
  612. {
  613.         short                            delta;
  614.         
  615.         delta = (width((**theControl).contrlRect) * 7) / 8;
  616.         switch (partcode) {
  617.         case inUpButton:    delta = -CharWidth('M');    break;
  618.         case inPageUp:        delta = -(delta);            break;        
  619.         case inDownButton:    delta = CharWidth('M');        break;
  620.         case inPageDown:    /* All set */                break;
  621.         default:            return;        /* Mouse exited control    */
  622.         }
  623.         SetCtlValue(theControl, GetCtlValue(theControl) + delta);
  624.         ScrollLogList(theControl);
  625. }
  626.  
  627. /*
  628.  * ScrollLogList scrolls the list rectangle in the proper direction and updates
  629.  * the list's horizontal indentation to match.
  630.  */
  631. static void
  632. ScrollLogList(
  633.         ControlHandle                    theControl
  634.     )
  635. {
  636.         ListHandle                        logListHandle;
  637.         short                            delta;
  638.         RgnHandle                        clipRgn;
  639.         RgnHandle                        updateRgn;
  640.         Rect                            viewRect;
  641.         
  642.         logListHandle = (ListHandle) GetCRefCon(theControl);
  643.         /*
  644.          * LIST.indent.h is negative when the cell is scrolled left. Get the
  645.          * amount it's currently scrolled (as a positive value) and set delta
  646.          * to the amount that must be scrolled. Delta will be positive to
  647.          * scroll right (which means that the scrollbar has moved left).
  648.          */
  649.         delta = kZeroIndent - LIST.indent.h - GetCtlValue(theControl);
  650.         if (delta != 0) {
  651.             /*
  652.              * We need to scroll the list cells. Get a clip rectangle so the
  653.              * scrolling is limited to the drawing area, scroll it, and update
  654.              * the stuff that came into view.
  655.              */
  656.             viewRect = LIST.rView;
  657.             clipRgn = NewRgn();
  658.             updateRgn = NewRgn();
  659.             GetClip(clipRgn);
  660.             ClipRect(&viewRect);
  661.             ScrollRect(&viewRect, delta, 0, updateRgn);
  662.             LIST.indent.h += delta;
  663.             LUpdate(updateRgn, logListHandle);
  664.             SetClip(clipRgn);
  665.             DisposeRgn(updateRgn);
  666.             DisposeRgn(clipRgn);
  667.         }
  668. }
  669.         
  670. /*
  671.  * Draw the string stored in the list. The only difference between this function
  672.  * and a "normal" LDEF is that we don't visually indicate selection.
  673.  */
  674. static pascal void
  675. LogListDefProc(
  676.         short                            listMessage,
  677.         Boolean                            listSelect,
  678.         Rect                            *listRect,
  679.         Cell                            listCell,
  680.         short                            listDataOffset,
  681.         short                            listDataLen,
  682.         ListHandle                        logListHandle
  683.     )
  684. {
  685.         char                            stringLockState;
  686.         RGBColor                        saveForeColor;
  687.         RGBColor                        saveBackColor;
  688.         StringHandle                    stringHandle;
  689.         
  690.         if (0) {            /* Touch unused variables    */
  691.             listCell;
  692.         }
  693.         /*
  694.          * If the userHandle isn't setup, do nothing: this is an initialization
  695.          * or a spurious command while we're disposing of the list.
  696.          */
  697.         if (LIST.userHandle != NULL) {
  698.             TextFont(LOGINFO.fontNumber);
  699.             TextSize(LOGINFO.fontSize);
  700.             if (COLOR_LIST) {
  701.                 GetForeColor(&saveForeColor);
  702.                 GetBackColor(&saveBackColor);
  703.                 RGBForeColor(&LOGINFO.foreColor);
  704.                 RGBBackColor(&LOGINFO.backColor);
  705.             }
  706.             switch (listMessage) {
  707.             case lInitMsg:
  708.                 break;
  709.             case lDrawMsg:
  710.                 EraseRect(listRect);
  711.                 if (listDataLen == sizeof stringHandle) {
  712.                     BlockMove(
  713.                         (*LIST.cells) + listDataOffset,
  714.                         &stringHandle,
  715.                         sizeof stringHandle
  716.                     );
  717.                     /*
  718.                      * We don't indent in the vertical direction: by default,
  719.                      * it contains the font ascent which is fine for DrawText
  720.                      */
  721.                     stringLockState = HGetState((Handle) stringHandle);
  722.                     HLock((Handle) stringHandle);
  723.                     MoveTo(
  724.                         listRect->left + LIST.indent.h,
  725.                         listRect->top + LIST.indent.v
  726.                     );
  727.                     DrawString(*stringHandle);
  728.                     HSetState((Handle) stringHandle, stringLockState);
  729.                 }
  730.                 if (listSelect == FALSE)
  731.                     break;
  732.                 /* Continue to do hilite */
  733.             case lHiliteMsg:
  734. #if 0        /* Hiliting is disabled */
  735. #ifdef THINK_C
  736.                 HiliteMode &= ~(1 << hiliteBit);
  737. #else /* MPW */
  738.                 *((char *) HiliteMode) &= ~(1 << hiliteBit); /* Inside Mac V-61    */
  739. #endif
  740.                 InvertRect(listRect);
  741. #endif
  742.                 break;
  743.             }
  744.             if (COLOR_LIST) {
  745.                 RGBForeColor(&saveForeColor);
  746.                 RGBBackColor(&saveBackColor);
  747.             }
  748.         }
  749. }
  750.  
  751. StringHandle
  752. GetLogStringHandle(
  753.         ListHandle                    logListHandle,
  754.         short                        theRow
  755.     )
  756. {
  757.         Cell                        theCell;
  758.         StringHandle                result;
  759.         short                        dataLength;
  760.         
  761.         dataLength = sizeof result;
  762.         theCell.h = 0;
  763.         theCell.v = theRow;
  764.         LGetCell(&result, &dataLength, theCell, logListHandle);
  765.         if (dataLength != sizeof result)
  766.             result = NULL;
  767.         return (result);
  768. }
  769.  
  770. /*
  771.  * Prompt the user for a file name and write the current log contents
  772.  * to the chosen file. Returns an error status (noErr is ok). This function
  773.  * returns userCanceledErr if the user cancelled the SFPutFile dialog.
  774.  */
  775. OSErr
  776. SaveLogFile(
  777.         ListHandle                        logListHandle,
  778.          ConstStr255Param                dialogPromptString,
  779.         ConstStr255Param                defaultFileName,
  780.         OSType                            creatorType
  781.     )
  782. {
  783.         Point                            where;
  784.         SFReply                            reply;
  785.         DialogTHndl                        dialog;
  786.         Rect                            box;
  787.         OSErr                            status;
  788.         
  789.         /*
  790.          * Center the dialog
  791.          */
  792.         dialog = (DialogTHndl) GetResource('DLOG', putDlgID);
  793.         if (dialog == NULL)                             /* No such dialog!    */
  794.             SetRect(&box, 0, 0, 0, 0);                /* Center on screen    */
  795.         else {
  796.             box = (*dialog)->boundsRect;             /* Dialog shape        */
  797.             ReleaseResource((Handle) dialog);        /* Done with dialog    */
  798.         }
  799.         SetPt(&where,
  800.             (width(qd.thePort->portRect) - width(box)) / 2,
  801.             ((height(qd.thePort->portRect) - GetMBarHeight()) / 3)
  802.                 + GetMBarHeight()
  803.         );
  804.         SFPutFile(where, dialogPromptString, defaultFileName, NULL, &reply);
  805.         if (reply.good == FALSE)
  806.             status = userCanceledErr;
  807.         else {
  808.             SetCursor(*GetCursor(watchCursor));
  809.             status = CreateLogFile(
  810.                         logListHandle,
  811.                         creatorType,
  812.                         reply.fName,
  813.                         reply.vRefNum
  814.                     );
  815.             InitCursor();
  816.             if (status != noErr)
  817.                 (void) FSDelete(reply.fName, reply.vRefNum);
  818.         }
  819.         return (status);
  820. }
  821.  
  822. /*
  823.  * Create a log on the specified file and volume. Normally, called only
  824.  * by SaveLogFile.
  825.  */
  826. OSErr
  827. CreateLogFile(
  828.         ListHandle                        logListHandle,
  829.         OSType                            creatorType,
  830.         ConstStr255Param                fileName,
  831.         short                            vRefNum
  832.     )
  833. {
  834.         OSErr                            status;
  835.         short                            refNum;
  836.         
  837.         /*
  838.          * Create the file, elmininating any duplicate.
  839.          */
  840.         if (creatorType == 0)
  841.             creatorType = 'ttxt';                    /* TeachText            */
  842.         status = Create(fileName, vRefNum, creatorType, 'TEXT');
  843.         if (status == dupFNErr) {                    /* Exists already?        */
  844.             status = FSDelete(fileName, vRefNum);
  845.             if (status == noErr)
  846.                 status = Create(fileName, vRefNum, creatorType, 'TEXT');
  847.         }
  848.         if (status == noErr)
  849.             status = FSOpen(fileName, vRefNum, &refNum);
  850.         if (status == noErr) {
  851.             LOGINFO.logFileRefNum = refNum;
  852.             LOGINFO.logFileVRefNum = vRefNum;
  853.             LOGINFO.logFileStatus = noErr;
  854.             WriteCurrentLog(logListHandle);
  855.             status = LOGINFO.logFileStatus;
  856.         }
  857.         if (status != noErr) {
  858.             (void) CloseLogFile(logListHandle);
  859.             LogStatus(logListHandle, status, "\pCreateLogFile failed");
  860.         }
  861.         return (status);
  862. }
  863.  
  864. /*
  865.  * Write the current contents of the log file. Any error will be
  866.  * in LOGINFO.logFileStatus.
  867.  */
  868. void
  869. WriteCurrentLog(
  870.         ListHandle                        logListHandle
  871.     )
  872. {
  873.         short                            theRow;
  874.         long                            fileLength;
  875.         StringHandle                    stringHandle;
  876.         char                            stringLockState;
  877.  
  878.         
  879.         theRow = LIST.dataBounds.top;
  880.         while (LOGINFO.logFileStatus == noErr && theRow < LIST.dataBounds.bottom) {
  881.             stringHandle = GetLogStringHandle(logListHandle, theRow);
  882.             if (stringHandle != NULL) {
  883.                 stringLockState = HGetState((Handle) stringHandle);
  884.                 HLock((Handle) stringHandle);
  885.                 fileLength = (*stringHandle)[0];
  886.                 LOGINFO.logFileStatus = FSWrite(
  887.                     LOGINFO.logFileRefNum,
  888.                     &fileLength,
  889.                     &(*stringHandle)[1]
  890.                 );
  891.                 HSetState((Handle) stringHandle, stringLockState);
  892.                 if (LOGINFO.logFileStatus == noErr) {
  893.                     fileLength = 1;
  894.                     LOGINFO.logFileStatus = FSWrite(
  895.                                 LOGINFO.logFileRefNum,
  896.                                 &fileLength,
  897.                                 endOfLine
  898.                             );
  899.                 }
  900.             }
  901.             ++theRow;
  902.         };
  903. }        
  904.  
  905. void
  906. WriteLogLine(
  907.         ListHandle                        logListHandle,
  908.         ConstStr255Param                theText
  909.     )
  910. {
  911.         long                            fileLength;
  912.         
  913.         if (LOGINFO.logFileRefNum != 0 && LOGINFO.logFileStatus == noErr) {
  914.             fileLength = theText[0];
  915.             LOGINFO.logFileStatus = FSWrite(
  916.                         LOGINFO.logFileRefNum,
  917.                         &fileLength,
  918.                         &theText[1]
  919.                     );
  920.             if (LOGINFO.logFileStatus == noErr) {
  921.                 fileLength = 1;
  922.                 LOGINFO.logFileStatus = FSWrite(
  923.                             LOGINFO.logFileRefNum,
  924.                             &fileLength,
  925.                             endOfLine
  926.                         );
  927.             }
  928.             if (LOGINFO.logFileStatus != noErr) {
  929.                 CloseLogFile(logListHandle);
  930.                 LogStatus(
  931.                     logListHandle,
  932.                     LOGINFO.logFileStatus,
  933.                     "\pCan't write to log file"
  934.                 );
  935.             }
  936.         }
  937. }
  938.  
  939. OSErr
  940. CloseLogFile(
  941.         ListHandle                        logListHandle
  942.     )
  943. {
  944.         OSErr                            status;
  945.         
  946.         status = noErr;
  947.         if (LOGINFO.logFileRefNum != 0) {
  948.             status = FSClose(LOGINFO.logFileRefNum);
  949.             if (status == noErr)
  950.                 status = FlushVol(NULL, LOGINFO.logFileVRefNum);
  951.         }
  952.         LOGINFO.logFileRefNum = 0;
  953.         LOGINFO.logFileVRefNum = 0;
  954.         if (status != noErr)
  955.             LogStatus(logListHandle, status, "\pCan't close log file");
  956.         if (LOGINFO.logFileStatus == noErr)
  957.             LOGINFO.logFileStatus = status;
  958.         status = LOGINFO.logFileStatus;
  959.         LOGINFO.logFileStatus = noErr;
  960.         return (status);
  961. }
  962.  
  963. OSErr
  964. GetLogFileError(
  965.         ListHandle                        logListHandle
  966.     )
  967. {
  968.         return (LOGINFO.logFileStatus);
  969. }
  970.  
  971. Boolean
  972. HasLogFile(
  973.         ListHandle                        logListHandle
  974.     )
  975. {
  976.         return (LOGINFO.logFileRefNum != 0);
  977. }
  978.  
  979. /*
  980.  * Copyright © 1993, Apple Computer Inc. All Rights reserved.
  981.  *
  982.  * This started life as a generic print driver written by Rich Siegel and posted
  983.  * to Usenet in around 1988 or so. It has been extensively rewritten, but not
  984.  * necessarily improved.
  985.  */
  986.  
  987. /*
  988.  *        OSErr
  989.  *        PrintDriver(
  990.  *                THPrint            hPrint,
  991.  *                void            *clientData,
  992.  *                Boolean            doStyle,
  993.  *                OSErr            (*userPrepProc)(
  994.  *                                        THPrnt        hPrint,
  995.  *                                        void        *clientData
  996.  *                                    ),
  997.  *                OSErr            (*userPageProc)(
  998.  *                                        THPrnt        hPrint,
  999.  *                                        void        *clientData,
  1000.  *                                        const Rect    *pageRect,
  1001.  *                                        short        pageNum
  1002.  *                                    )
  1003.  *            );
  1004.  *        hPrint        A Print Manager handle.  If NULL, ReportPrintDriver
  1005.  *                    will allocate a handle, call the Page Setup dialog,
  1006.  *                    and dispose of the handle on exit.
  1007.  *        clientData    A longword parameter that becomes a parameter to the
  1008.  *                    prepProc and pageProc functions. For the LogManager,
  1009.  *                    it is the ListHandle.
  1010.  *        doStyle        If TRUE, the page setup modal dialog is always called.
  1011.  *                    Otherwise, it's called only if the print record is invalid.
  1012.  *
  1013.  *        userPrepProc A user-provided function that will perform print setup
  1014.  *                    initializations.  It must be defined
  1015.  *                        OSErr            UserPrepProc(
  1016.  *                                THPrint        hPrint,
  1017.  *                                void        *clientData
  1018.  *                            );
  1019.  *                    UserPrepProc returns noErr on success or an error status that
  1020.  *                    will  be returned to the PrintDriver caller on failure.
  1021.  *                    UserPrepProc must set (**hPrint).prJob.iLstPage  to the correct
  1022.  *                    number of pages in the document. If all pages are specified
  1023.  *                    by the user dialog, this field will be 999 when UserPrepProc
  1024.  *                    is called; it must then be re-set to the correct value.
  1025.  *        userPageProc A user-provided function that draws the page.  It is defined:
  1026.  *                        OSErr            UserPageProc(
  1027.  *                                THPrint        hPrint,
  1028.  *                                void        *clientData,
  1029.  *                                Rect        *pageRect,
  1030.  *                                short        pageNum
  1031.  *                        );
  1032.  *                    UserPageProc returns noErr if it completed correctly, or an
  1033.  *                    error code that will be returned to the user.  It should
  1034.  *                    return iPrAbort on user-specified aborts.
  1035.  */
  1036.  
  1037. /*
  1038.  * Note: the serial stuff has not been tested in several years.
  1039.  */
  1040. #ifndef bDevCItoh
  1041. #define bDevCItoh        1                /* ImageWriter                */
  1042. #endif
  1043. #ifndef bDevLaser
  1044. #define bDevLaser        3                /* LaserWriter                */
  1045. #endif
  1046. #define ImageWriter        (bDevCItoh)
  1047. #define    LaserWriter        (bDevLaser)
  1048.  
  1049. OSErr                                PrintLog(
  1050.         ListHandle                        logListHandle,
  1051.         THPrint                            hPrint
  1052.     );
  1053. OSErr                                LogPrepProc(
  1054.         THPrint                            hPrint,
  1055.         void                            *clientData
  1056.     );
  1057. OSErr                                LogPageProc(
  1058.         THPrint                            hPrint,
  1059.         void                            *clientData,
  1060.         const Rect                        *pageRect,
  1061.         short                            pageNumber
  1062.     );
  1063. OSErr                                PrintDriver(
  1064.         THPrint                            hPrint,
  1065.         Boolean                            doStyleDialog,
  1066.         void                            *clientData,
  1067.         OSErr                            (*userPrepProc)(
  1068.                         THPrint        hPrint,
  1069.                         void        *clientData
  1070.                     ),
  1071.         OSErr                            (*userPageProc)(
  1072.                         THPrint        hPrint,
  1073.                         void        *clientData,
  1074.                         const Rect    *pageRect,
  1075.                         short        pageNumber
  1076.                     )
  1077.     );
  1078.  
  1079. /*
  1080.  * PrintLog, LogPrepProc, and LogPageProc are specific to the LogManager.
  1081.  */
  1082.  
  1083. OSErr
  1084. PrintLog(
  1085.         ListHandle                        logListHandle,
  1086.         THPrint                            hPrint
  1087.     )
  1088. {
  1089.         OSErr            status;
  1090.         
  1091.         status = PrintDriver(
  1092.                     hPrint,
  1093.                     FALSE,
  1094.                     logListHandle,
  1095.                     LogPrepProc,
  1096.                     LogPageProc
  1097.                 );
  1098.         return (status);
  1099. }
  1100.  
  1101. OSErr
  1102. LogPrepProc(
  1103.         THPrint                        hPrint,
  1104.         void                        *clientData
  1105.     )
  1106. {
  1107.         unsigned short                linesInLog;
  1108.         unsigned short                linesPerPage;
  1109.         unsigned short                pagesInLog;
  1110.         Rect                        printRect;
  1111. #define logListHandle    ((ListHandle) clientData)
  1112.  
  1113.         /*
  1114.          * We could add a page header here, of course.
  1115.          */
  1116.         printRect = (**hPrint).prInfo.rPage;
  1117.         linesInLog = height(LIST.dataBounds);
  1118.         linesPerPage = height(printRect) / LIST.cellSize.v;
  1119.         pagesInLog = (linesInLog + linesPerPage - 1) / linesPerPage;
  1120.         (**hPrint).prJob.iLstPage = pagesInLog;
  1121.         return (noErr);
  1122. #undef logListHandle
  1123. }
  1124.  
  1125. OSErr
  1126. LogPageProc(
  1127.         THPrint                            hPrint,
  1128.         void                            *clientData,
  1129.         const Rect                        *pageRect,
  1130.         short                            pageNumber
  1131.     )
  1132. {
  1133.         unsigned short                    linesPerPage;
  1134.         short                            lastCell;
  1135.         Point                            drawLoc;
  1136.         FontInfo                        info;
  1137.         short                            theRow;
  1138.         StringHandle                    stringHandle;
  1139.         char                            stringLockState;
  1140. #define logListHandle    ((ListHandle) clientData)
  1141.  
  1142.         if (0) {                /* Touch unused variables    */
  1143.             hPrint;
  1144.         }
  1145.         linesPerPage = height(*pageRect) / LIST.cellSize.v;
  1146.         TextFont(LOGINFO.fontNumber);
  1147.         TextSize(LOGINFO.fontSize);
  1148.         GetFontInfo(&info);
  1149.         theRow = (pageNumber - 1) * linesPerPage + LIST.dataBounds.top;
  1150.         lastCell = theRow + linesPerPage - 1;
  1151.         if (lastCell > LIST.dataBounds.bottom)
  1152.             lastCell = LIST.dataBounds.bottom;
  1153.         SetPt(
  1154.             &drawLoc,
  1155.             LIST.indent.h,
  1156.             pageRect->top + info.ascent
  1157.         );
  1158.         /*
  1159.          * We could add a page header here, of course.
  1160.          */
  1161.         for (; theRow < lastCell; ++theRow) {
  1162.             stringHandle = GetLogStringHandle(logListHandle, theRow);
  1163.             if (stringHandle != NULL) {
  1164.                 stringLockState = HGetState((Handle) stringHandle);
  1165.                 HLock((Handle) stringHandle);
  1166.                 MoveTo(drawLoc.h, drawLoc.v);
  1167.                 DrawString(*stringHandle);
  1168.                 HSetState((Handle) stringHandle, stringLockState);
  1169.             }
  1170.             drawLoc.v += LIST.cellSize.v;
  1171.         };
  1172.         return (PrError());
  1173. }
  1174.  
  1175. /*
  1176.  * Rich Siegel's generic printer driver, somewhat modified.
  1177.  */
  1178. OSErr
  1179. PrintDriver(
  1180.         THPrint                            hPrint,
  1181.         Boolean                            doStyleDialog,
  1182.         void                            *clientData,
  1183.         OSErr                            (*userPrepProc)(
  1184.                         THPrint        hPrint,
  1185.                         void        *clientData
  1186.                     ),
  1187.         OSErr                            (*userPageProc)(
  1188.                         THPrint        hPrint,
  1189.                         void        *clientData,
  1190.                         const Rect    *pageRect,
  1191.                         short        pageNumber
  1192.                     )
  1193.     )
  1194. {
  1195.         OSErr                status;                /* Random status                */
  1196.         Boolean                ourPrintHandle;        /* Did we allocate hPrint        */
  1197.         Boolean                printIsOpen;        /* PROpen-PRClose                 */        
  1198.         Boolean                docIsOpen;            /* PROpenDoc-PRCloseDoc            */
  1199.         Boolean                pageIsOpen;            /* PROpenPage-PRClosePage        */
  1200.         short                nCopies;            /* Number of copies             */
  1201.         short                printDevice;        /* What kind of printer            */
  1202.         Boolean                draftMode;            /* Draft or Spool?                */
  1203.         TPPrPort            printPort;            /* The print port                */
  1204.         TPrStatus            printStatus;        /* PrPicFile status info        */
  1205.         GrafPtr                savePort;            /* Old GrafPort                    */
  1206.         short                iCopy;                /* Which copy                    */
  1207.         short                page;                /* Which page                     */
  1208.         Rect                pageRect;            /* Current page image rect        */
  1209. /*
  1210.  * This macro exits the print handler on any error.
  1211.  */
  1212. #define    CheckError(s)    do {                \
  1213.         if ((status = (s)) != noErr)        \
  1214.             goto exit;                        \
  1215.     } while (0);
  1216.  
  1217.         GetPort(&savePort);
  1218.         status = noErr;
  1219.         printIsOpen = FALSE;
  1220.         docIsOpen = FALSE;
  1221.         pageIsOpen = FALSE;
  1222.         ourPrintHandle = (hPrint == NULL);
  1223.         PrOpen();
  1224.         CheckError(PrError());
  1225.         printIsOpen = TRUE;
  1226.         /*
  1227.          * Allocate the print handle if necessary.  memFullErr on failure
  1228.          * it failed.
  1229.          */
  1230.         if (ourPrintHandle) {
  1231.             hPrint = (THPrint) NewHandle(sizeof (TPrint));
  1232.             if (hPrint == NULL) {
  1233.                 status = MemError();
  1234.                 goto exit;
  1235.             }
  1236.             PrintDefault(hPrint);
  1237.         }
  1238.         /*
  1239.          * Validate the print handle and call the Style Dialog if necessary.  If
  1240.          * the user cancels, just exit (noErr). Then call the job dialog to get
  1241.          * the number of copies. Exit (noErr) if the user Cancels. (Strictly
  1242.          * speaking, we could exit with userCanceledErr, but that just forces
  1243.          * the caller to ignore this informational status.
  1244.          */
  1245.         if (PrValidate(hPrint) || doStyleDialog) {
  1246.             if (PrStlDialog(hPrint) == FALSE)
  1247.                 goto exit;
  1248.         }
  1249.         if (PrJobDialog(hPrint) == FALSE)
  1250.             goto exit;
  1251.         /*
  1252.          * Our setup is done. call the user's prep procedure and exit on errors.
  1253.          */
  1254.         SetCursor(*GetCursor(watchCursor));
  1255.         CheckError((*userPrepProc)(hPrint, clientData));
  1256.         /*
  1257.          * Grab a few interesting parameters:
  1258.          *    printDevice    Which printer we're using
  1259.          *    is_draftMode    TRUE if we're in draft-mode.
  1260.          *    nCopies            We do copies if we're in draft-mode on an Imagewriter.
  1261.          *                    Otherwise, the spooler does it for us. This hasn't
  1262.          *                    been tested in years.
  1263.          */
  1264.         printDevice = ((**hPrint).prStl.wDev >> 8) & 0xFF;
  1265.         draftMode = (**hPrint).prJob.bJDocLoop == bDraftLoop;
  1266.         if (draftMode && printDevice == ImageWriter)
  1267.             nCopies = (**hPrint).prJob.iCopies;
  1268.         else {
  1269.             nCopies = 1;
  1270.         }
  1271.         /*
  1272.          * Printing begins here.  PrOpenDoc sets the port.
  1273.          */
  1274.         printPort = PrOpenDoc(hPrint, NULL, NULL);
  1275.         docIsOpen = TRUE;
  1276.         CheckError(PrError());
  1277.         for (iCopy = 1; iCopy <= nCopies; iCopy++) {
  1278.             for (page = (**hPrint).prJob.iFstPage;
  1279.                         page <= (**hPrint).prJob.iLstPage;
  1280.                         page++) {
  1281.                 SetCursor(*GetCursor(watchCursor));
  1282.                 /*
  1283.                  * Print the current page.
  1284.                  */
  1285.                 PrOpenPage(printPort, NULL);
  1286.                 pageIsOpen = TRUE;
  1287.                 pageRect = (**hPrint).prInfo.rPage;
  1288.                 status = (*userPageProc)(hPrint, clientData, &pageRect, page);
  1289.                 PrClosePage(printPort);
  1290.                 pageIsOpen = FALSE;
  1291.                 CheckError(status);
  1292.             }
  1293.         }
  1294.         SetPort(savePort);
  1295.         PrCloseDoc(printPort);
  1296.         docIsOpen = FALSE;
  1297.         status = PrError();
  1298.         if (draftMode == FALSE && status == noErr) {
  1299.             PrPicFile(hPrint, NULL, NULL, NULL, &printStatus);
  1300.             status = PrError();
  1301.         }
  1302.         /*
  1303.          * Everyone exits here
  1304.          */
  1305. exit:    SetPort(savePort);
  1306.         InitCursor();
  1307.         if (pageIsOpen)
  1308.             PrClosePage(printPort);
  1309.         if (docIsOpen)
  1310.             PrCloseDoc(printPort);
  1311.         if (printIsOpen)
  1312.             PrClose();
  1313.         if (ourPrintHandle && hPrint != NULL)
  1314.             DisposHandle((Handle) hPrint);
  1315.         return (status);
  1316. }
  1317.  
  1318.